﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Web;

using umbraco.BusinessLogic;
using umbraco.cms.businesslogic.media;
using umbraco.cms.businesslogic.property;
using umbraco.cms.businesslogic.web;
using System.Configuration;
using System.Web.Configuration;
using System.Xml;
using umbraco.IO;


namespace idseefeld.com.images
{
    public static class ImageToolHelper
    {
        public static string BuildOriganlName(string fileName, string fileExtension)
        {

            return fileName.Replace("." + fileExtension, "_original." + fileExtension);
        }
    }
    public class ImageTools
    {
        private const string umbracoFilePropertyName = "umbracoFile";
        private const string originalUploadPropertyName = "originalUploadFile";
        private const string useOriginalUploadProp = "useOriginalUpload";
        private const string cropPrefix = "_cb_auto_";
        private const string thumbPrefix = "_thumb_";
        private const bool useSharpen = true;
        private const bool useIcm = true;//true for more color saturation

        private int cropBaseWidth = 800;
        private int cropBaseHeight = 700;
        private IDseefeldImagesConfig config = null;

        private Media img;

        public ImageTools(Media img, IDseefeldImagesConfig config)
        {
            this.img = img;
            this.config = config;
            if (config.ReducedHeight > 0) this.cropBaseHeight = config.ReducedHeight;
            if (config.ReducedWidth > 0) this.cropBaseWidth = config.ReducedWidth;
        }
        public ImageTools(Media img)
        {
            this.img = img;
        }
        public ImageTools()
        {
            this.img = null;
        }
        public string GeneratImageByWidth(int newWidth, UmbracoImage umbImage)
        {
            string result = umbImage.Src;
            string newSrc = umbImage.Src.Substring(0, umbImage.Src.LastIndexOf('.')) + "_autoWidth" + newWidth + "." + umbImage.Extension;
            string newPath = HttpContext.Current.Server.MapPath(newSrc);
            if (File.Exists(newPath))
                return newSrc;

            int newHeight = (int)((double)newWidth / (double)umbImage.Width * (double)umbImage.Height);
            if (saveNewImageSize(
                HttpContext.Current.Server.MapPath(umbImage.Src),
                umbImage.Extension,
                newPath,
                newWidth, false, false, umbImage.Width))
                result = newSrc;

            return result;
        }
        public void GenerateImages()
        {

            UmbracoImage umbImg = new UmbracoImage(img);

            if (img.getProperty(originalUploadPropertyName) == null)
                return;

            Property prop = img.getProperty(useOriginalUploadProp);
            bool useOriginalUpload = (prop != null && prop.Value.ToString() == "1");

            string fileExtension = umbImg.Extension.ToLower();
            string fileName = umbImg.Src.ToLower();

            if (!String.IsNullOrEmpty(fileName)
                && !String.IsNullOrEmpty(fileExtension)
                && (",jpeg,jpg,gif,bmp,png,tiff,tif,".IndexOf("," + fileExtension + ",") > -1)
                )
            {

                string imgPath = HttpContext.Current.Server.MapPath(fileName);
                if (!File.Exists(imgPath))
                    return;

                //render resized images only as jpeg or png
                string newFileExtension = "jpg";
                if (fileExtension.Equals("png"))
                    newFileExtension = fileExtension;

                //create backend crop base size
                string originalName = img.getProperty(originalUploadPropertyName).Value.ToString();
                if (String.IsNullOrEmpty(originalName))
                    originalName = ImageToolHelper.BuildOriganlName(fileName, fileExtension);
                string originalPath = HttpContext.Current.Server.MapPath(originalName);
                if (!File.Exists(originalPath))
                    File.Move(imgPath, originalPath);
                img.getProperty(originalUploadPropertyName).Value = originalName;

                string resizeName = fileName.Replace("." + fileExtension, "." + newFileExtension);
                img.getProperty(umbracoFilePropertyName).Value = resizeName;
                string resizePath = HttpContext.Current.Server.MapPath(resizeName);
                int newWidth = cropBaseWidth;
                if (umbImg.Height > umbImg.Width)
                    newWidth = (int)Math.Round((double)umbImg.Width / umbImg.Height * cropBaseHeight, 0);

                saveNewImageSize(originalPath, newFileExtension, resizePath, newWidth, false, useOriginalUpload, umbImg.Width);
            }
        }

        private InterpolationMode GetInterpolationMode(int oldWidth, int newWidth)
        {
            InterpolationMode iMode = InterpolationMode.Default;
            if (oldWidth == 0)
                return InterpolationMode.HighQualityBicubic;

            double resizeFactor = (double)newWidth / (double)oldWidth;
            if (resizeFactor > 0.25)
                iMode = InterpolationMode.HighQualityBicubic;
            if (resizeFactor > 0.5)
                iMode = InterpolationMode.HighQualityBilinear;

            return iMode;
        }

        private void generateAutoCrops(string imgPath, string fileExtension, string cropName, int newWidth, bool useOriginalUpload, int oldWidth)
        {
            string newPath = (!useOriginalUpload) ?
                imgPath.Replace(cropName, cropPrefix + newWidth) :
                imgPath.Replace("." + fileExtension, cropPrefix + newWidth + "." + fileExtension);
            saveNewImageSize(imgPath, fileExtension, newPath, newWidth, true, useOriginalUpload, oldWidth);
        }

        private ImageCodecInfo GetEncoder(ImageFormat format)
        {

            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();

            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.FormatID == format.Guid)
                {
                    return codec;
                }
            }
            return null;
        }
        public bool saveNewImageSize(string imgPath, string fileExtension, string newPath, int newWidth, bool forceResize, bool useOriginalUpload, int oldWidth)
        {
            return saveNewImageSize(imgPath, fileExtension, newPath, newWidth, forceResize, useOriginalUpload, oldWidth, 0);
        }
        public bool saveNewImageSize(string imgPath, string fileExtension, string newPath, int newWidth, bool forceResize, bool useOriginalUpload, int oldWidth, int sharpness)
        {
            return saveCroppedNewImageSize(imgPath, fileExtension, newPath, newWidth, forceResize, useOriginalUpload, oldWidth,
                0, 0, 0, 0, 0, 100L, sharpness);
        }
        public bool saveCroppedNewImageSize(string imgPath, string fileExtension, string newPath, int newWidth, bool forceResize, bool useOriginalUpload, int oldWidth,
           int sizeHeight, int cropX, int cropY, int cropWidth, int cropHeight, long quality)
        {
            return saveCroppedNewImageSize(imgPath, fileExtension, newPath, newWidth, forceResize, useOriginalUpload, oldWidth,
            sizeHeight, cropX, cropY, cropWidth, cropHeight, quality, 0);
        }
        
        
        public bool saveCroppedNewImageSize(
            string imgPath, 
            string fileExtension, string newPath, 
            int newWidth, bool forceResize, bool useOriginalUpload, int oldWidth, 
            int sizeHeight, 
            int cropX, int cropY, int cropWidth, int cropHeight, 
            long quality, int sharpness)
        {
            bool newImgSaved = false;
            InterpolationMode iMode = GetInterpolationMode(oldWidth, newWidth);
            try
            {
                //use Icm for original colors
                using (Bitmap orig = new Bitmap(imgPath, useIcm))
                {
                    int newHeight;

                    #region encoder settings
                    ImageFormat format = ImageFormat.Jpeg;
                    ImageCodecInfo imgEncoder = GetEncoder(ImageFormat.Jpeg);
                    EncoderParameter imgEncoderParameter;
                    EncoderParameters imgEncoderParameters;
                    System.Drawing.Imaging.Encoder qualtiyEncoder =
                        System.Drawing.Imaging.Encoder.Quality;
                    switch (fileExtension)
                    {
                        case "tiff":
                        case "tif":
                            format = ImageFormat.Tiff;
                            imgEncoder = GetEncoder(ImageFormat.Tiff);
                            break;
                        case "png":
                            format = ImageFormat.Png;
                            imgEncoder = GetEncoder(ImageFormat.Png);
                            break;
                        case "gif":
                            format = ImageFormat.Gif;
                            imgEncoder = GetEncoder(ImageFormat.Gif);
                            break;
                        default:
                            format = ImageFormat.Jpeg;
                            imgEncoder = GetEncoder(ImageFormat.Jpeg);
                            break;
                    }
                    imgEncoderParameters = new EncoderParameters(1);

                    imgEncoderParameter = new EncoderParameter(qualtiyEncoder, quality);

                    imgEncoderParameters.Param[0] = imgEncoderParameter;
                    #endregion

                    if (!useOriginalUpload
                        && (forceResize || orig.Width > newWidth))
                    {
                        newHeight = (int)Math.Round(((double)newWidth) / orig.Width * orig.Height);

                        int cW = newWidth;
                        int cH = newHeight;
                        if (cropWidth > 0)
                        {
                            cW = cropWidth;
                            cH = cropHeight;
                            newHeight = sizeHeight;
                        }
                        using (Bitmap crop = new Bitmap(cW, cH))
                        {
                            if (cropWidth > 0)
                            {
                                Graphics graphCrop = Graphics.FromImage(crop);

                                graphCrop.SmoothingMode = SmoothingMode.HighQuality;
                                //graphCrop.InterpolationMode = InterpolationMode.HighQualityBicubic;
                                graphCrop.PixelOffsetMode = PixelOffsetMode.HighQuality;

                                graphCrop.InterpolationMode = iMode;
                                GraphicsUnit units = GraphicsUnit.Pixel;
                                graphCrop.DrawImage(orig, new Rectangle(0, 0, cW, cH), cropX, cropY, cW, cH, units);
                            }

                            using (Bitmap bmp = new Bitmap(newWidth, newHeight))
                            {
                                Graphics graph = Graphics.FromImage(bmp);
                                graph.SmoothingMode = SmoothingMode.HighQuality;
                                graph.PixelOffsetMode = PixelOffsetMode.HighQuality;
                                graph.InterpolationMode = iMode;
                                if (cropWidth > 0)
                                    graph.DrawImage(crop, new Rectangle(0, 0, newWidth, newHeight));
                                else
                                    graph.DrawImage(orig, new Rectangle(0, 0, newWidth, newHeight));
                                if (File.Exists(newPath))
                                    File.Delete(newPath);
                                if (useSharpen
                                    && (newWidth > 100)
                                    && fileExtension.ToLower() != "png"
                                    && sharpness > 0)
                                {
                                    using (Bitmap sbmp = Sharpen(bmp, sharpness))
                                    {
                                        sbmp.Save(newPath, imgEncoder, imgEncoderParameters);
                                    }
                                }
                                else
                                    bmp.Save(newPath, imgEncoder, imgEncoderParameters);
                                newImgSaved = true;
                            }
                        }

                    }
                    else
                    {
                        if (File.Exists(newPath))
                            File.Delete(newPath);
                        File.Copy(imgPath, newPath);
                        newImgSaved = true;
                    }
                }
            }
            catch { }
            return newImgSaved;
        }
        //based on code source: http://stackoverflow.com/questions/903632/sharpen-on-a-bitmap-using-c
        private Bitmap Sharpen(Bitmap image, int sharpness)
        {
            //Bitmap sharpenImage = (Bitmap)image.Clone();
            int width = image.Width;
            int height = image.Height;
            int tempAdd = 3;
            int tempWidth = width + tempAdd * 2;
            int tempHeight = height + tempAdd * 2;

            Bitmap sharpenImage = new Bitmap(tempWidth, tempHeight);
            Graphics graph = Graphics.FromImage(sharpenImage);
            graph.InterpolationMode = InterpolationMode.Bilinear;
            graph.DrawImage(image, new Rectangle(0, 0, tempWidth, tempHeight));
            graph.DrawImage(image, new Rectangle(tempAdd, tempAdd, width, height));

            //define filter
            int sL = sharpness > 0 ? sharpness : 64;//smaller values for more sharpness, bigger values for less sharpness
            const int filterWidth = 5;
            const int filterHeight = 5;
            double[,] filter = new double[filterWidth, filterHeight] {
			    { -1, -1, -1, -1, -1 },
			    { -1,  2,  2,  2, -1 },
			    { -1,  2, sL,  2, -1 },
			    { -1,  2,  2,  2, -1 },
			    { -1, -1, -1, -1, -1 }
			};
            double factor = 1.0 / sL;
            double bias = 1.0;//negative values darken, positive values lighten image

            Color[,] result = new Color[tempWidth, tempHeight];
            // Lock image bits for read/write.
            BitmapData pbits = sharpenImage.LockBits(new Rectangle(0, 0, tempWidth, tempHeight),
                ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            // Declare an array to hold the bytes of the bitmap.
            int bytes = pbits.Stride * tempHeight;
            int channelOffset = 3;
            byte[] rgbValues = new byte[bytes];
            // Copy the RGB values into the array.
            System.Runtime.InteropServices.Marshal.Copy(pbits.Scan0, rgbValues, 0, bytes);
            int rgb;
            // Fill the color array with the new sharpened color values.
            for (int x = 0; x < tempWidth; ++x)
            {
                for (int y = 0; y < tempHeight; ++y)
                {
                    double red = 0.0, green = 0.0, blue = 0.0;
                    for (int filterX = 0; filterX < filterWidth; filterX++)
                    {
                        for (int filterY = 0; filterY < filterHeight; filterY++)
                        {
                            int imageX = (x - filterWidth / 2 + filterX + tempWidth) % tempWidth;
                            int imageY = (y - filterHeight / 2 + filterY + tempHeight) % tempHeight;

                            rgb = imageY * pbits.Stride + channelOffset * imageX;

                            red += rgbValues[rgb + 2] * filter[filterX, filterY];
                            green += rgbValues[rgb + 1] * filter[filterX, filterY];
                            blue += rgbValues[rgb + 0] * filter[filterX, filterY];
                        }
                    }
                    int r = Math.Min(Math.Max((int)(factor * red + bias), 0), 255);
                    int g = Math.Min(Math.Max((int)(factor * green + bias), 0), 255);
                    int b = Math.Min(Math.Max((int)(factor * blue + bias), 0), 255);
                    result[x, y] = Color.FromArgb(r, g, b);
                }
            }
            // Update the image with the sharpened pixels.
            for (int x = 0; x < tempWidth; ++x)
            {
                for (int y = 0; y < tempHeight; ++y)
                {
                    rgb = y * pbits.Stride + channelOffset * x;

                    rgbValues[rgb + 2] = result[x, y].R;
                    rgbValues[rgb + 1] = result[x, y].G;
                    rgbValues[rgb + 0] = result[x, y].B;
                }
            }
            // Copy the RGB values back to the bitmap.
            System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, pbits.Scan0, bytes);
            // Release image bits.
            sharpenImage.UnlockBits(pbits);

            Bitmap resultImage = new Bitmap(width, height);
            Graphics resultGraph = Graphics.FromImage(resultImage);
            resultGraph.InterpolationMode = InterpolationMode.Bilinear;
            resultGraph.DrawImage(sharpenImage, new Rectangle(-tempAdd, -tempAdd, tempWidth, tempHeight));
            return resultImage;
        }
    }

}